%%%-------------------------------------------------------------------
%%% File    : ewgi_yaws.erl
%%% Authors : Filippo Pacini <pacini@sgconsulting.it>
%%%           Hunter Morris <huntermorris@gmail.com>
%%% License :
%%% The contents of this file are subject to the Mozilla Public
%%% License Version 1.1 (the "License"); you may not use this file
%%% except in compliance with the License. You may obtain a copy of
%%% the License at http://www.mozilla.org/MPL/
%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and
%%% limitations under the License.
%%% The Initial Developer of the Original Code is S.G. Consulting
%%% srl. Portions created by S.G. Consulting s.r.l. are Copyright (C)
%%% 2007 S.G. Consulting srl. All Rights Reserved.
%%%
%%% @doc 
%%% <p>Sample implementation of a yaws server gateway.</p>
%%%
%%% @end
%%%
%%% Created : 12 Oct 2007 by Filippo Pacini <pacini@sgconsulting.it>
%%%-------------------------------------------------------------------
-module(ewgi_yaws, [Appl]).

%% ewgi api and callbacks
-export([setup_environ/1,
         start_response/1,
         run/1,
         write/2,
         end_response/1
        ]).

-include_lib("yaws/include/yaws_api.hrl").
-include("../include/ewgi.hrl").

%%====================================================================
%% ewgi_server callbacks
%%====================================================================
run(Arg) ->
    Env = setup_environ(Arg),
    error_logger:info_report(Env),    
    OutBuf = ewgi_buffer:start(),
    Result = Appl(Env, start_response(OutBuf)),
    handle_result(OutBuf, Result).

handle_result(OutBuf, F) when is_function(F, 0) ->
    handle_stream_result(OutBuf, F());
handle_result(OutBuf, {}) ->
    end_response(OutBuf);
handle_result(OutBuf, Chunk) when is_list(Chunk); is_binary(Chunk) ->
    write(OutBuf, Chunk).

handle_stream_result(OutBuf, {}) ->
    end_response(OutBuf);
handle_stream_result(OutBuf, {H, T}) ->
    write(OutBuf, H),
    handle_stream_result(OutBuf, T()).

setup_environ(Req) ->
    parse_arg(Req).

start_response(OutBuffer) ->
    Resp = fun (?OK, []) ->
                   void;
               (?OK, Headers) ->
                   ewgi_buffer:append(OutBuffer, Headers);
               (Status, Headers) ->
                   ewgi_buffer:append(OutBuffer, [{status, Status}, Headers])
           end,
    Resp.

write(OutBuffer, Result) ->
    ewgi_buffer:append(OutBuffer, {html, Result}).

end_response(OutBuf) ->
    OutputData = ewgi_buffer:close(OutBuf),
    OutputData.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
parse_arg(Req) ->
    Hdrs = parse_headers(Req),
    lists:foldl(fun({El, ElName}, PList) ->
                        V = parse_element(El, Req),
                        case V of
                            undefined -> PList;
                            [] -> PList;
                            V ->
                                NewEl = proplists:property({ElName, V}),
                                [NewEl|PList]
                        end
                end, Hdrs, ?EWGI_ENV_DATA).

parse_element(server_sw, _Req) ->
    "Yaws";

parse_element(gw_interface, _Req) ->
    "EWGI/1.0";

parse_element(server_name, Req) ->
    H = Req#arg.headers,
    HostPort = H#headers.host,
    hd(string:tokens(HostPort, ":"));

parse_element(server_port, Req) ->
    Headers = Req#arg.headers,
    HostPort = string:tokens(Headers#headers.host, ":"),
    case length(HostPort) of
        2 -> [_H, P] = HostPort, P;
        _ -> undefined
    end;

parse_element(server_protocol, Req) ->
    R = Req#arg.req,
    {Maj, Min} = R#http_request.version,
    lists:flatten(io_lib:format("HTTP/~b.~b", [Maj, Min]));

parse_element(method, Req) ->
    R = Req#arg.req,
    R#http_request.method;

parse_element(path_info, Req) ->
    Req#arg.pathinfo;

parse_element(path_translated, Req) ->
    Req#arg.fullpath;

parse_element(script_name, Req) ->
    Req#arg.prepath;

parse_element(qs, Req) ->
    Req#arg.querydata;

parse_element(content_type, Req) ->
    H = Req#arg.headers,
    H#headers.content_type;

parse_element(content_length, Req) ->
    H = Req#arg.headers,
    H#headers.content_length;

parse_element(remote_host, _Req) ->
    undefined;

parse_element(remote_addr, Req) ->
    {{A,B,C,D}, _Port} = Req#arg.client_ip_port,
    lists:flatten(io_lib:format("~b.~b.~b.~b", [A,B,C,D]));

parse_element(auth_type, Req) ->
    H = Req#arg.headers,
    H#headers.authorization;

parse_element(remote_user, _Req) ->
    undefined;

parse_element(remote_ident, _Req) ->
    undefined;

parse_element(read_input, Req) ->
    Buf = Req#arg.clidata,
    F = fun(Callback, Length) when is_integer(Length) -> % No chunk size specified, so use default
                read_input(Callback, {Length, ?DEFAULT_CHUNKSIZE}, Buf);
           (Callback, {Length, ChunkSz}) ->
                read_input(Callback, {Length, ChunkSz}, Buf)
        end,
    F;

parse_element(ewgi_version, _Req) ->
    {1, 0};

%% http or https?
parse_element(url_scheme, _Req) ->
    undefined.

parse_headers(Req) ->
    Hs = Req#arg.headers,
    Headers = [connection,
               accept,
               if_modified_since,
               if_match,
               if_none_match,
               if_range,
               if_unmodified_since,
               range,
               referer,
               user_agent,
               accept_ranges,
               cookie,
               keep_alive,
               location,
               content_encoding,
               transfer_encoding
              ],
    ParsedHs = lists:foldl(fun(El, PList) ->
                                   V = parse_headers(El, Hs),
                                   case V of
                                       undefined -> PList;
                                       [] -> PList;
                                       V ->
                                           {K, V1} = ewgi_api:normalize_header({El, V}),
                                           NewEl = proplists:property({lists:append(["HTTP_", K]), V1}),
                                           [NewEl|PList]
                                   end
                           end, [], Headers),
    lists:foldl(fun(El, PList2) ->
                        {K, V} = ewgi_api:normalize_header({element(3, El), element(5, El)}),
                        NewEl = proplists:property({lists:append(["HTTP_", K]), V}),
                        [NewEl|PList2]
                end, ParsedHs, Hs#headers.other).

parse_headers(connection, Header) ->
    Header#headers.connection;
parse_headers(accept, Header) ->
    Header#headers.accept;
parse_headers(if_modified_since, Header) ->
    Header#headers.if_modified_since;
parse_headers(if_match, Header) ->
    Header#headers.if_match;
parse_headers(if_none_match, Header) ->
    Header#headers.if_none_match;
parse_headers(if_range, Header) ->
    Header#headers.if_range;
parse_headers(if_unmodified_since, Header) ->
    Header#headers.if_unmodified_since;
parse_headers(range, Header) ->
    Header#headers.range;
parse_headers(referer, Header) ->
    Header#headers.referer;
parse_headers(user_agent, Header) ->
    Header#headers.user_agent;
parse_headers(accept_ranges, Header) ->
    Header#headers.accept_ranges;
parse_headers(cookie, Header) ->
    Header#headers.cookie;
parse_headers(keep_alive, Header) ->
    Header#headers.keep_alive;
parse_headers(location, Header) ->
    Header#headers.location;
parse_headers(content_encoding, Header) ->
    Header#headers.content_encoding;
parse_headers(transfer_encoding, Header) ->
    Header#headers.transfer_encoding.

%% Final callback after entire input has been read
read_input(Callback, {Length, _ChunkSz}, _Left) when is_function(Callback), Length =< 0 ->
    Callback(eof);

%% Continue reading and calling back with each chunk of data
read_input(Callback, {Length, ChunkSz}, Left) when is_function(Callback) ->
    L = recv_size(Length, ChunkSz),
    <<Bin:L/bytes,Rest/bits>> = Left,
    Rem = Length - size(Bin),
    NewCallback = Callback({data, Bin}),
    read_input(NewCallback, {Rem, ChunkSz}, Rest).

%% Read either Length bytes or ChunkSz, whichever is smaller
recv_size(Length, ChunkSz) when Length > 0, Length < ChunkSz ->
    Length;
recv_size(_, ChunkSz) ->
    ChunkSz.

